package com.senriot.quickup.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.querydsl.core.BooleanBuilder
import com.senriot.cloud.aliyun.DeviceManager
import com.senriot.cloud.common.AbstractService
import com.senriot.cloud.common.error.BadRequestAlertException
import com.senriot.cloud.common.result.R
import com.senriot.cloud.common.result.fail
import com.senriot.cloud.common.result.success
import com.senriot.quickup.domain.Device
import com.senriot.quickup.domain.DeviceMessage
import com.senriot.quickup.domain.QDevice
import com.senriot.quickup.domain.QDeviceMessage
import com.senriot.quickup.domain.enums.DevStatus
import com.senriot.quickup.repository.BonusRepository
import com.senriot.quickup.repository.DeviceRepository
import com.senriot.quickup.repository.MessageRepository
import com.senriot.quickup.service.dto.BonusDTO
import com.senriot.quickup.service.dto.DeviceDTO
import com.senriot.quickup.service.dto.DeviceLogDTO
import com.senriot.quickup.service.dto.DeviceStatistics
import com.senriot.quickup.service.mapper.AliyunMapper
import com.senriot.quickup.service.mapper.DeviceLogMapper
import com.senriot.quickup.service.mapper.DeviceMapper
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import reactor.event.Event
import reactor.function.Consumer
import java.math.BigDecimal
import java.nio.charset.Charset
import java.time.LocalDate
import java.util.*

/**
 * Service Implementation for managing [Device].
 */
@Service
@Transactional
class DeviceService(
        private val aliyunMapper: AliyunMapper,
        private val deviceManager: DeviceManager,
        private val messageRepository: MessageRepository,
        private val deviceLogMapper: DeviceLogMapper,
        private val bonusRepository: BonusRepository,
        private val objectMapper: ObjectMapper
) : AbstractService<Device, String, DeviceDTO, DeviceRepository, DeviceMapper>(), Consumer<Event<DeviceMessage>>
{
    override fun accept(t: Event<DeviceMessage>)
    {

    }


    @Value("\${seri.iot.productKey:a1DRsxixYff}")
    private var productKey = ""


    override fun create(dto: DeviceDTO): DeviceDTO
    {
        return Optional.ofNullable(
                deviceManager.registerDevice(
                        ProductKey = productKey,
                        DeviceName = dto.deviceId!!,
                        Nickname = dto.name
                )
        ).map {
            if (it.success)
            {
                var dev = aliyunMapper.registerDevice(it.data, mapper.toEntity(dto))
//                for (i in 1..dto.channelCount!!) {
//                    dev.channels[i] = Channel(i)
//                }
                dev = repository.save(dev)
                mapper.toDto(dev)
            } else
            {
                throw RuntimeException(it.errorMessage)
            }
        }.orElseThrow { RuntimeException("注册设备失败") }
    }

    override fun delete(id: String): R<String>
    {
        return Optional.ofNullable(deviceManager.deleteDevice(IotId = id)).map {
            if (it.success)
            {
                repository.deleteById(id)
                success<String>()
            } else
            {
                fail(it.errorMessage)
            }
        }.orElseThrow { RuntimeException("设备服务异常") }
    }

    fun queryDeviceStatistics(): DeviceStatistics
    {
//        return Optional.ofNullable(deviceManager.queryDeviceStatistics(productKey)).map {
//            if (it.success)
//            {
//                it.data
//            } else
//            {
//                throw RuntimeException(it.errorMessage)
//            }
//        }.orElseThrow { RuntimeException("设备服务异常") }

        val result = DeviceStatistics()
        result.deviceCount = repository.count()
        result.activeCount = repository.count(QDevice.device.status.ne(DevStatus.UNACTIVE))
        result.onlineCount = repository.count(QDevice.device.status.eq(DevStatus.ONLINE))
        return result
    }

    fun disableThing(id: String)
    {
        Optional.ofNullable(deviceManager.disableThing(IotId = id)).map {
            if (!it.success)
                throw RuntimeException(it.errorMessage)
        }.orElseThrow { RuntimeException("设备服务异常") }
    }

    fun enableThing(id: String)
    {
        Optional.ofNullable(deviceManager.enableThing(IotId = id)).map {
            if (!it.success)
                throw RuntimeException(it.errorMessage)
        }.orElseThrow { RuntimeException("设备服务异常") }
    }

//    fun getChannels(deviceId: String): List<Channel> {
//        return repository.findById(deviceId).map { it.channels.values.toList() }.orElseThrow { RuntimeException("没找到相关设备") }
//    }

    fun getLogs(
            pageable: Pageable,
            deviceId: String,
            fromDate: LocalDate? = null,
            toDate: LocalDate? = null
    ): Page<DeviceLogDTO>
    {
        val msg = QDeviceMessage.deviceMessage
        val builder = BooleanBuilder(msg.productKey.eq(productKey))
        builder.and(msg.device.id.eq(deviceId))
        fromDate?.let { builder.and(msg.generateTime.between(fromDate.atTime(0, 0), toDate?.atTime(0, 0))) }
        return messageRepository.findAll(builder, pageable).map { deviceLogMapper.toDto(it) }
    }

    fun playService(deviceId: String, req: MutableMap<String, Any>): MutableMap<String, Any>?
    {
        val str = objectMapper.writeValueAsString(req)
        val base64 = Base64.getEncoder().encode(str.toByteArray(Charset.defaultCharset()))
        val res = deviceManager.rRpc(productKey, deviceId, base64.toString(Charset.defaultCharset()))
        if (res.success)
        {
            val result = Base64.getDecoder().decode(res.payloadBase64Byte)
            return objectMapper.readValue<MutableMap<String, Any>>(result)
        } else
        {
            throw BadRequestAlertException(res.errorMessage, "device", "rrpc-error")
        }
    }

    /**
     * 获取可以推送广告的设备
     * @return List<DeviceDTO>
     */
    fun loadHasAds(): List<DeviceDTO>
    {
        return repository.findAll(QDevice.device.product.hasAd.eq(true)).map { mapper.toDto(it) }
    }

    /**
     * 批量修改单价
     * @param ids List<String> 设备ID
     * @param price BigDecimal 单价
     */
    fun updatePrice(ids: List<String>, price: BigDecimal): MutableList<Device>
    {
        val s = repository.findAllById(ids).map {
            it.price = price
            it
        }
        return repository.saveAll(s)
    }

    /**
     * 获取设备分成信息
     * @param id String
     * @return List<BonusDTO>
     */
    fun getDeviceBonus(id: String): MutableList<BonusDTO>?
    {
        return repository.findById(id).map {
            mapper.bonusToDto(it.bonuses)
        }.orElseThrow {
            RuntimeException("设备没找到")
        }
    }

    /**
     * 添加分成信息
     * @param dto BonusDTO
     * @return MutableList<BonusDTO>?
     */
    fun addBonus(dto: BonusDTO): MutableList<BonusDTO>?
    {
        return repository.findById(dto.deviceId!!).map {
            val item = mapper.bonusToEntity(dto)
            it.bonuses.add(item)
            val dev = repository.saveAndFlush(it)
            mapper.bonusToDto(dev.bonuses)
        }.orElseThrow {
            RuntimeException("设备没找到")
        }
    }

    /**
     * 修改分成信息
     * @param dto BonusDTO
     * @return MutableList<BonusDTO>?
     */
    fun updateBonus(dto: BonusDTO): BonusDTO?
    {
        return bonusRepository.findById(dto.id!!).map {
            it.amount = dto.amount
            val result = bonusRepository.save(it)
            mapper.bonusToDto(result)
        }.orElseThrow { RuntimeException() }
    }

    /**
     * 删除分成
     * @param id Long
     */
    fun deleteBonus(id: Long): Result<Unit>
    {
        return try
        {
            Result.success(bonusRepository.deleteById(id))
        } catch (e: Exception)
        {
            Result.failure(e)
        }
    }


    fun manyUpdateBonus(deviceId: String, dtos: List<BonusDTO>): Optional<List<BonusDTO>>?
    {
        return repository.findById(deviceId).map { device ->

            bonusRepository.saveAll(mapper.bonusToEntities(dtos)).map { mapper.bonusToDto(it) }
//            mapper.bonusToEntities(dtos).forEach {
//                log.info("$it")
//                device.addBonus(it)
//            }
//            val dev = repository.save(device)
//            mapper.bonusToDto(dev.bonuses)
        }
    }
}
